Extension { #name : 'Context' }

{ #category : '*Debugging-Core' }
Context >> callPrimitive: primNumber [
	| res |
	res := receiver tryPrimitive: primNumber withArgs: self arguments.
	primNumber >= (1 << 15) ifTrue: [ "inlined primitive, cannot fail" ^ self push: res ].  
	"regular primitive. Always at the beginning of methods."
	(self isFailToken: res) ifTrue: [ "keeps interpreting the method" ^ self ].
	self push: res.
	self methodReturnTop
]

{ #category : '*Debugging-Core' }
Context >> findSecondToOldestSimilarSender [
	"Search the stack for the second-to-oldest occurance of self's method.  Very useful for an infinite recursion.  Gets back to the second call so you can see one complete recursion cycle, and how it was called at the beginning."

	| secondContext context lastContext |
	secondContext := self.
	context := self.
	[	lastContext := context findSimilarSender.
		lastContext isNil
	] whileFalse: [
		secondContext := context.
		context := lastContext.
	].
	^ secondContext
]

{ #category : '*Debugging-Core' }
Context >> findSimilarSender [
	"Return the closest sender with the same method, return nil if none found"

	^ self sender findContextSuchThat: [ :context | 
		context compiledCode == method ]
]

{ #category : '*Debugging-Core' }
Context >> hasTemporaryVariableNamed: aName [
	^ self temporaryVariableNames includes: aName
]

{ #category : '*Debugging-Core' }
Context >> instructionStream [
	
	^ InstructionStream on: method pc: pc
]

{ #category : '*Debugging-Core' }
Context >> methodReturnConstant: value [
	"Simulate the action of a 'return constant' bytecode whose value is the
	 argument, value. This corresponds to a source expression like '^0'."

	^self return: value from: self home
]

{ #category : '*Debugging-Core' }
Context >> pcRangeContextIsActive: contextIsActive [
	"return the debug highlight for aPC"

	| pcToHighlight |
	"If the context is suspended on a send, we want to highlight the send.
	However, the context already executed the send, and the PC advanced to the next instruction.
	Thus, we want the instruction previously executed!"
	pcToHighlight := contextIsActive
		                 ifTrue: [ self currentPC ]
		                 ifFalse: [ self executedPC ].
	^ self method debugInfo rangeForPC: pcToHighlight
]

{ #category : '*Debugging-Core' }
Context >> readVariableNamed: aName [
	^ (self lookupVar: aName) readInContext: self
]

{ #category : '*Debugging-Core' }
Context >> respondsToUnknownBytecode [
	"This method is triggerred by the VM when the interpreter tries to execute an unknown bytecode"
	
	| unknownBytecode |
	unknownBytecode := self compiledCode at: self pc.
	self error: 'VM cannot run unknown bytecode ', unknownBytecode printString
]

{ #category : '*Debugging-Core' }
Context >> restart [
	"Unwind thisContext to self and resume from beginning.  Execute unwind blocks when unwinding.  ASSUMES self is a sender of thisContext"

	| context unwindBlock |
	self isDead ifTrue: [self cannotReturn: nil to: self].
	self privRefresh.
	context := thisContext.
	[	context := context findNextUnwindContextUpTo: self.
		context isNil 
	] whileFalse: [
		context unwindComplete ifNil:[
			context unwindComplete: true.
			unwindBlock := context unwindBlock.
			thisContext terminateTo: context.
			unwindBlock value ]].
	
	thisContext terminateTo: self.
	self jump.
]

{ #category : '*Debugging-Core' }
Context >> restartWithNewReceiver: obj [

	self
		swapReceiver: obj;
		restart
]

{ #category : '*Debugging-Core' }
Context >> returnBooleanPrimitive [

	^ 257
]

{ #category : '*Debugging-Core' }
Context >> returnSelfPrimitive [

	^ 256
]

{ #category : '*Debugging-Core' }
Context class >> runSimulated: aBlock [
	"Simulate the execution of the argument, current. Answer the result it 
	returns."

	^ thisContext sender
		runSimulated: aBlock
		contextAtEachStep: [:ignored |]

	"Context runSimulated: [Pen new defaultNib: 5; go: 100]"
]

{ #category : '*Debugging-Core' }
Context >> runSimulated: aBlock contextAtEachStep: block2 [
	"Simulate the execution of the argument, aBlock, until it ends. aBlock 
	MUST NOT contain an '^'. Evaluate block2 with the current context 
	prior to each instruction executed. Answer the simulated value of aBlock."
	| current returnContext exception |
	
	aBlock hasNonLocalReturn
		ifTrue: [ self error: 'simulation of blocks with ^ can run loose' ].
		
	current := [ aBlock
					on: Exception
					do: [ :ex | SimulationExceptionWrapper signalForException: ex  ] ] asContext.
	
	returnContext := Context
			sender: nil
			receiver: self home receiver
			method: self home compiledCode
			arguments: self home arguments.
	
	current pushArgs: Array new from: returnContext.
	
	[current == returnContext]
		whileFalse: [ 
			block2 value: current.
			current := current step ].
	
	exception := returnContext pop.
	exception class == SimulationExceptionWrapper
		ifTrue: [ ^ exception exception signal ].
	^ exception
]

{ #category : '*Debugging-Core' }
Context >> send: selector super: superFlag numArgs: numArgs [
	"Simulate the action of bytecodes that send a message with selector, 
	selector. The argument, superFlag, tells whether the receiver of the 
	message was specified with 'super' in the source method. The arguments 
	of the message are found in the top numArgs locations on the stack and 
	the receiver just below them."

	| currentReceiver arguments  |
	arguments := Array new: numArgs.
	numArgs to: 1 by: -1 do: [ :i | 
		arguments at: i put: self pop ].
	currentReceiver := self pop.
"	selector == #doPrimitive:method:receiver:args:
		ifTrue: [answer := receiver 
					doPrimitive: (arguments at: 1)
					method: (arguments at: 2)
					receiver: (arguments at: 3)
					args: (arguments at: 4).
				self push: answer.
				^self]. "
	^ self send: selector to: currentReceiver with: arguments super: superFlag
]

{ #category : '*Debugging-Core' }
Context >> stepIntoQuickMethod [
	^self compiledCode stepIntoQuickMethods
]

{ #category : '*Debugging-Core' }
Context >> stepIntoQuickMethod: aBoolean [

	self compiledCode stepIntoQuickMethods: aBoolean
]

{ #category : '*Debugging-Core' }
Context >> stepToHome: aContext [
	"Resume self until the home of top context is aContext.  Top context may be a block context."

	| home ctxt here error wrapperContext |
	here := thisContext.
	error := nil.
	
	wrapperContext := aContext insertSender: (Context contextOn: Exception do: [ :ex | 
				            error
					            ifNil: [ 
						            			error := ex.
												ex resumeUnchecked: here jump ]
					            ifNotNil: [ 
										ex pass ] ]).
	home := aContext home.

	"A first Step to do at least one step"
	ctxt := self step.

	home == ctxt home ifTrue: [ 
		"remove above inserted ensure and handler contexts"
		aContext terminateTo: wrapperContext sender.
		^ { 
			  ctxt.
			  error } ].

	[  ctxt := ctxt step.
		error ifNotNil: [ 
		"remove above inserted ensure and handler contexts"
		aContext terminateTo: wrapperContext sender.
			^ { 
				  ctxt.
				  error } ].

		home == ctxt home ] whileFalse: [ 
			(home isDead or: [ ctxt willFailReturn ]) ifTrue: [ 
				^ { 
					  ctxt.
					  nil } ] ].
	"remove above inserted ensure and handler contexts"
	aContext terminateTo: wrapperContext sender.
	
	^ { 
		  ctxt.
		  nil }
]

{ #category : '*Debugging-Core' }
Context >> stepToSendOrReturn [
	"Simulate the execution of bytecodes until either sending a message or 
	returning a value to the receiver (that is, until switching contexts)."

	| stream context |
	stream := InstructionStream on: method pc: pc.	
	[ self isDead or: [ stream willSend or: [ stream willReturn or: [ stream willStore or: [ stream willCreateBlock ] ] ] ] ]
		whileFalse: [
			context := stream interpretNextInstructionFor: self.
			context == self ifFalse: [
				"Caused by mustBeBoolean handling"
				^context ]]
]

{ #category : '*Debugging-Core' }
Context class >> tallyInstructions: aBlock [
	"Count the occurrences of each bytecode during the execution of aBlock.
	Return a Array of associations using the byte as key and the occurrences as values sorted by the instruction opcode numeric values.
	
	This method uses the in-image bytecode interpreter to evaluate and count the instructions."
	
	"(Context tallyInstructions: [3.14159 printString]) size >>> 120"
	
	| tallies |
	tallies := Bag new.
	thisContext sender
		runSimulated: aBlock
		contextAtEachStep:
			[:current | tallies add: current instructionStream peekByte ].
	^tallies sortedElements
]

{ #category : '*Debugging-Core' }
Context class >> tallyMethods: aBlock [
	"This method uses the simulator to count the number of calls on each method
	invoked in evaluating aBlock. Results are given in order of decreasing counts."
	| prev tallies |
	tallies := Bag new.
	prev := aBlock.
	thisContext sender
		runSimulated: aBlock
		contextAtEachStep:
			[:current |
			current == prev ifFalse: "call or return"
				[prev sender ifNotNil: "call only"
					[tallies add: current printString].
				prev := current]].
	^ tallies sortedCounts

	"Contex tallyMethods: [3.14159 printString]"
]

{ #category : '*Debugging-Core' }
Context >> tempNamed: aName [
	"Returns the value of the temporaries, aName"

	| debugInfo |
	debugInfo := self method debugInfo.
	^ debugInfo readVariableNamed: aName fromContext: self
]

{ #category : '*Debugging-Core' }
Context >> tempNamed: aName put: anObject [
	"Assign the value of the temp with name in aContext"
	| var |
	var := self lookupVar: aName.
	"To be checked. if we keep the error, we should raise a dedicated exception, 
	but no client would catch it, so why do we need it at all?"
 	var isLocalVariable ifFalse: [ ^self error: var name, ' is not a temp but, ', var class name].
	^var write: anObject inContext: self
]

{ #category : '*Debugging-Core' }
Context >> temporaryVariableNamed: aName [
	(self hasTemporaryVariableNamed: aName)
		ifFalse: [ ^ nil ].
	^self lookupVar: aName
]

{ #category : '*Debugging-Core' }
Context >> temporaryVariableNames [

	"If this context did not yet execute, consider the initial PC"
	| suspendedPC |
	suspendedPC := self executedPC max: self method initialPC.
	^ self method debugInfo variableNamesAt: suspendedPC
]

{ #category : '*Debugging-Core' }
Context >> tempsAndValuesLimitedTo: sizeLimit indent: indent [
	"Return a string of the temporary variabls and their current values"

	^ String streamContents: [ :aStream |
			  | variableNames |
			  variableNames := self temporaryVariableNames.
			  variableNames
				  do: [ :name |
						  indent timesRepeat: [ aStream tab ].
						  aStream
							  nextPutAll: name;
							  nextPut: $:;
							  space;
							  tab.
						  aStream nextPutAll:
							  ((self tempNamed: name) printStringLimitedTo:
								   (sizeLimit - 3 - name size max: 1)) ]
				  separatedBy: [ aStream cr ] ]
]

{ #category : '*Debugging-Core' }
Context >> unusedBytecode [
	^ self respondsToUnknownBytecode
]

{ #category : '*Debugging-Core' }
Context >> updatePCForQuickPrimitiveRestart [

	({self returnBooleanPrimitive.	self returnSelfPrimitive } includes: self method primitive) 
		ifTrue: [ 
			pc := self method endPC.
			^ self ].

	pc := pc + self quickMethodPrimitiveBytecodeSize
]
